import java.util.ArrayList;
import java.util.Collections;

public class Hand implements Comparable<Hand> {

	public static String[] TYPE_NAMES = {"High Card", "Pair", "Two Pair",
		"Three of a Kind", "Straight", "Flush", "Full House", "Four of a Kind",
		"Straight Flush"};
	public static final int HIGH = 0;
	public static final int PAIR = 1;
	public static final int TWO_PAIR = 2;
	public static final int THREE_OF_A_KIND = 3;
	public static final int STRAIGHT = 4;
	public static final int FLUSH = 5;
	public static final int FULL_HOUSE = 6;
	public static final int FOUR_OF_A_KIND = 7;
	public static final int STRAIGHT_FLUSH = 8;
	public static final int HAND_SIZE = 5;
	/** 
	 * 6 unsigned 4-bit integers packed into a signed 32-bit integer,
	 * attributes which take precedence have higher-significance bits for easy
	 * comparison. Order is tier, rank within tier, up to 4 kickers
	 */
	private int value;
	private Card[] cards;  //Cards used in the hand
	public Hand(Card[] car)
	{
		cards = car.clone();
		value = evaluateHand();
	}
	public Hand(ArrayList<Card> car)
	{
		cards = new Card[car.size()];
		for (int i = 0; i < cards.length; i++)
			cards[i] = car.get(i);
		value = evaluateHand();
	}

	public int compareTo(Hand h)
	{
		if (h == null)
			return 1;
		else if (h.value > value)
			return -1;
		else if (h.value < value)
			return 1;
		else
			return 0;
	}
	
	public int getValue()
	{
		return value;
	}
	
	public int getType()
	{
		return value >> 20;
	}

	public boolean inHand(Card c)
	{
		boolean inhand = false;
		for (Card c2 : cards)
		{
			if (c2.equals(c))
				inhand = true;
		}
		return inhand;
	}

	public Hand merge(Hand h)
	{
		if (cards.length + h.cards.length > HAND_SIZE)
			return null;

		ArrayList<Card> cs= new ArrayList<Card>();
		for (Card card:cards)
			cs.add(card);
		for (Card card:h.cards)
			cs.add(card);

		Hand h2 = new Hand((Card[]) cs.toArray());
		return h2;
	}

	public String toString()
	{
		//return typeString() + " : " + highCard().valueString();
		return typeString();
	}

	public String typeString()
	{
		try {
			return TYPE_NAMES[getType()];
		} catch (ArrayIndexOutOfBoundsException e) {
			return "Invalid hand value";
		}
	}

	/**
	 * Evaluates the hand and returns the value as an int.
	 */
	public int evaluateHand()
	{
		if (cards.length > HAND_SIZE)
			return -1;
		boolean flush = checkFlush(cards);
		int[] ranks = convertRanks(cards);
		//Special case: A2345 straight
		if (ranks[0] == 2 && ranks[1] == 3 && ranks[2] == 4 && ranks[3] == 5 && ranks[4] == 14)
			return flush ? 0x850000 : 0x550000;
		boolean straight = checkStraight(ranks);
		int[] resultArray = calcHandValue(ranks, straight, flush);
		int resultPacked = resultArray[0];
		for (int i = 1; i < resultArray.length; i++)
		{
			resultPacked <<= 4;
			if (resultArray[i] >= 0)
				resultPacked += resultArray[i];
		}
		return resultPacked;
	}
	//Returns ints: Tier, rank within tier, up to 4 kickers
	private static int[] calcHandValue(int[] ranks, boolean straight, boolean flush)
	{
		//flush and straight flush
		if (flush)
		{
			int[] result = {FLUSH, ranks[4], ranks[3], ranks[2], ranks[1], ranks[0]};
			if (straight)
				result[0] = STRAIGHT_FLUSH;
			return result;
		}
		//4 of a kind, kicker > four cards
		if (ranks[0] == ranks[1] && ranks[1] == ranks[2] && ranks[2] == ranks[3])
		{
			int[] result = {FOUR_OF_A_KIND, ranks[0], ranks[4], 0, 0, 0};
			return result;
		}
		//4 of a kind, kicker < four cards
		if (ranks[4] == ranks[1] && ranks[1] == ranks[2] && ranks[2] == ranks[3])
		{
			int[] result = {FOUR_OF_A_KIND, ranks[4], ranks[0], 0, 0, 0};
			return result;
		}
		//full house, lower set
		if (ranks[0] == ranks[1] && ranks[1] == ranks[2] && ranks[3] == ranks[4])
		{
			int[] result = {FULL_HOUSE, ranks[0], ranks[4], 0, 0, 0};
			return result;
		}
		//full house, higher set
		if (ranks[0] == ranks[1] && ranks[3] == ranks[2] && ranks[3] == ranks[4])
		{
			int[] result = {FULL_HOUSE, ranks[4], ranks[0], 0, 0, 0};
			return result;
		}
		if (straight)
		{
			int[] result = {STRAIGHT, ranks[4], 0, 0, 0, 0};
			return result;
		}
		//3 of a kind, set lowest
		if (ranks[0] == ranks[1] && ranks[1] == ranks[2])
		{
			int[] result = {THREE_OF_A_KIND, ranks[0], ranks[4], ranks[3], 0, 0};
			return result;
		}
		//3 of a kind, set middle
		if (ranks[3] == ranks[1] && ranks[1] == ranks[2])
		{
			int[] result = {THREE_OF_A_KIND, ranks[1], ranks[4], ranks[0], 0, 0};
			return result;
		}
		//3 of a kind, set highest
		if (ranks[3] == ranks[4] && ranks[3] == ranks[2])
		{
			int[] result = {THREE_OF_A_KIND, ranks[4], ranks[1], ranks[0], 0, 0};
			return result;
		}
		//2 pair, low kicker
		if (ranks[1] == ranks[2] && ranks[3] == ranks[4])
		{
			int[] result = {TWO_PAIR, ranks[4], ranks[2], ranks[0], 0, 0};
			return result;
		}
		//2 pair, middle kicker
		if (ranks[1] == ranks[0] && ranks[3] == ranks[4])
		{
			int[] result = {TWO_PAIR, ranks[4], ranks[1], ranks[2], 0, 0};
			return result;
		}
		//2 pair, high kicker
		if (ranks[1] == ranks[0] && ranks[3] == ranks[2])
		{
			int[] result = {TWO_PAIR, ranks[3], ranks[1], ranks[4], 0, 0};
			return result;
		}
		//pair, lowest
		if (ranks[1] == ranks[0])
		{
			int[] result = {PAIR, ranks[1], ranks[4], ranks[3], ranks[2], 0};
			return result;
		}
		//pair, 2nd lowest
		if (ranks[1] == ranks[2])
		{
			int[] result = {PAIR, ranks[1], ranks[4], ranks[3], ranks[0], 0};
			return result;
		}
		//pair, 2nd highest
		if (ranks[3] == ranks[2])
		{
			int[] result = {PAIR, ranks[3], ranks[4], ranks[1], ranks[0], 0};
			return result;
		}
		//pair, highest
		if (ranks[3] == ranks[4])
		{
			int[] result = {PAIR, ranks[3], ranks[2], ranks[1], ranks[0], 0};
			return result;
		}
		//high card
		else
		{
			int[] result = {HIGH, ranks[4], ranks[3], ranks[2], ranks[1], ranks[0]};
			return result;
		}
	}
	private static boolean checkFlush(Card[] hand)
	{
		if (hand.length == 0)
			return false;
		Card.Suit suit = hand[0].suit;
		for (int y = 1; y < hand.length; y++)
			if (hand[y].suit != suit)
				return false;
		return true;
	}
	private static boolean checkStraight(int[] ranks)
	{
		for (int x = 1; x < ranks.length; x++)
			if (ranks[x-1]+1 != ranks[x])
				return false;
		return true;
	}
	private static int[] convertRanks(Card[] hand)
	{
		ArrayList<Integer> ranks = new ArrayList<Integer>();
		for (int x = 0; x < hand.length; x++)
			ranks.add(hand[x].value);
		for (int x = hand.length, temp = 0; x < HAND_SIZE; x++, temp--)
			ranks.add(temp);
		Collections.sort(ranks);
		int[] result = new int[5];
		for (int x = 0; x < result.length; x++)
			result[x] = ranks.get(x).intValue();
		return result;
	}

	/**
	 * Finds the best hand in the list and returns it.
	 */
	public static Hand bestHand(ArrayList<Hand> hands)
	{
		if (hands == null ||hands.size() == 0)
			return null;
		if (hands.size() == 1)
			return hands.get(0);
		Hand high = hands.get(0);
		for (Hand hand:hands)
		{
			if (hand.compareTo(high) > 0)
				high = hand;
		}
		return high;
	}

	/**
	 * Finds the best 5-card hand that can possibly be made using the cards in
	 * the list of 5 or more and returns it.
	 * Note: To evaluate 4 cards or less, simply create the hand and use
	 * methods getType() and getValue().
	 * Warning: This runs in O(n^5) time, so it's probably best not to call it
	 * with a big list.
	 */
	public static Hand getBestHand(ArrayList<Card> cards)
	{
		if (cards.size() <= HAND_SIZE)
			return new Hand(cards);
		int[] i = new int[HAND_SIZE];
		Card[] cardsSub = new Card[HAND_SIZE];
		Hand best = null, test = null;
		//Iterate through all possible 5-hand combinations and return the best hand.
		for (i[0] = 0; i[0] <= cards.size() - HAND_SIZE; i[0]++)
		{
			cardsSub[0] = cards.get(i[0]);
			for (i[1] = i[0]+1; i[1] <= cards.size() - HAND_SIZE + 1; i[1]++)
			{
				cardsSub[1] = cards.get(i[1]);
				for (i[2] = i[1]+1; i[2] <= cards.size() - HAND_SIZE + 2; i[2]++)
				{
					cardsSub[2] = cards.get(i[2]);
					for (i[3] = i[2]+1; i[3] <= cards.size() - HAND_SIZE + 3; i[3]++)
					{
						cardsSub[3] = cards.get(i[3]);
						for (i[4] = i[3]+1; i[4] <= cards.size() - HAND_SIZE + 4; i[4]++)
						{
							cardsSub[4] = cards.get(i[4]);
							test = new Hand(cardsSub);
							if (test.compareTo(best) >= 1)
								best = test;
						}
					}
				}
			}
		}
		return best;
	}
}
